package sample.ws.util;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import sample.ws.entity.AccessRuleRepository;
import sample.ws.entity.IPRuleRepository;
import sample.ws.entity.ServiceAccessRuleEntity;
import sample.ws.entity.ServiceIPRuleEntity;
import sample.ws.model.RuleModel;

import javax.annotation.PostConstruct;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * ds..
 *
 * @author Lizhong
 * @date 2019/7/15
 */
@Component
public class AccessComponent {

    private ConcurrentHashMap<Long, Rule> ruleCache = new ConcurrentHashMap<>(10);
    private ConcurrentHashMap<Long, LoadingCache<String, AtomicInteger>> expireCache = new ConcurrentHashMap<>();
    private ConcurrentHashMap<Long, Set<String>> ipRuleCache = new ConcurrentHashMap<>();

    // private RuleModel rule;

    private final AccessRuleRepository accessRuleRepository;
    private final IPRuleRepository ipRuleRepository;

    public AccessComponent(AccessRuleRepository accessRuleRepository, IPRuleRepository ipRuleRepository) {
        this.accessRuleRepository = accessRuleRepository;
        this.ipRuleRepository = ipRuleRepository;
    }

    @PostConstruct
    public void initAll() {
        loadAccessRule();
        loadIpRule();
    }

    public void updateAccessRule(RuleModel rule) {
        if (rule.getDuration() < 0 || rule.getTimes() < 0) {
            expireCache.remove(rule.getServiceId());
            ruleCache.remove(rule.getServiceId());
            return;
        }
        expireCache.put(rule.getServiceId(), createCache(rule.getDuration()));
        ruleCache.put(rule.getServiceId(), new Rule(rule.getTimes(), rule.getDuration()));
    }

    void loadAccessRule() {
        expireCache.clear();
        ruleCache.clear();
        List<ServiceAccessRuleEntity> list = accessRuleRepository.findAllByEnableIsTrue();
        list.forEach(val -> {
            expireCache.put(val.getServiceId(), createCache(val.getDuration()));
            ruleCache.put(val.getServiceId(), new Rule(val.getTimes(), val.getDuration()));
        });
    }

    private LoadingCache<String, AtomicInteger> createCache(long duration) {
        return CacheBuilder.newBuilder()
                .maximumSize(200)// 设置缓存个数
                .expireAfterWrite(duration, TimeUnit.SECONDS)
                .build(new CacheLoader<String, AtomicInteger>() {
                    @Override
                    public AtomicInteger load(String s) throws Exception {
                        // 10 秒钟访问2次
                        return new AtomicInteger();
                    }
                });
    }

    void loadIpRule() {
        ipRuleCache.clear();
        List<ServiceIPRuleEntity> list = ipRuleRepository.findAllByEnableIsTrue();
        list.forEach(val -> {
            Set<String> ips = ipRuleCache.get(val.getServiceId());
            if (ips != null) {
                ips.addAll(IPUtil.getAvaliIpList(val.getIp()));
                return;
            }
            ipRuleCache.put(val.getServiceId(), IPUtil.getAvaliIpList(val.getIp()));
        });
    }

    public void updateIpRule(Long id) {
        ipRuleCache.remove(id);
        List<ServiceIPRuleEntity> list = ipRuleRepository.findByServiceIdAndEnableIsTrue(id);
        Set<String> objects = new HashSet<>();
        list.forEach(val -> objects.addAll(IPUtil.getAvaliIpList(val.getIp())));
        ipRuleCache.put(id, objects);
    }


    public boolean hasAccessible(String ip, Long serviceId) {

        Set<String> ips = this.ipRuleCache.get(serviceId);

        if (!ipMatch(ip, ips)) return false;

        try {
            LoadingCache<String, AtomicInteger> cache = expireCache.get(serviceId);
            if (cache == null) return true;
            AtomicInteger rateLimiter = cache.get(ip + ":" + serviceId);
            Rule rule = ruleCache.get(serviceId);
            return (rateLimiter.getAndIncrement() < rule.times);
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        return false;

    }

    public boolean ipMatch(String ip, Set<String> ipList) {

        return StringUtils.isEmpty(ip) || ObjectUtils.isEmpty(ipList) || IPUtil.checkLoginIP(ip, ipList);
    }

    @AllArgsConstructor
    private class Rule {
        private int times;
        private long duration;
    }


}
